//=============================================================================
// System  : MatchKit
// File    : DefaultTypeMappings.cs
// Author  : Fabio Bussu  (matchkit@fabiobussu.it)
// Updated : 04/16/2013
// Note    : Copyright 2013, Fabio Bussu, All rights reserved
//
// This code is published under the Microsoft Public License (Ms-PL). A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://matchkit.codeplex.com. This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//
// Version     Date     Who  Comments
// ============================================================================
// 1.0.0.0  04/16/2013   FB  First draft
//=============================================================================
using System;
using MatchKit.Patterns;
using System.Collections.Generic;
using MatchKit.ObjectMapping.Patterns;

namespace MatchKit.ObjectMapping
{
	/// <summary>
	/// Provides static methods to manage the default mappings between a type and a pattern.
	/// </summary>
	/// <threadsafety static="false" />
	/// <remarks>
	/// The mapping process explores attributes of matchable members to determine which 
	/// patterns use to match every member. In case of missing attributes, the mapping process 
	/// query for a default registration. The following types has a default pattern registered:
	/// <list type="table">
	///		<listheader>
	///			<term>Type</term>
	///			<description>Registered default pattern</description>
	///		</listheader>
	///		<item>
	///			<term>System.String</term>
	///			<description>If member value is null, <see cref="T:MatchKit.Patterns.WordPattern"/>; otherwise <see cref="T:MatchKit.Patterns.StringPattern"/></description>
	///		</item>
	///		<item>
	///			<term>System.Char</term>
	///			<description><see cref="T:MatchKit.Patterns.CharPattern"/></description>
	///		</item>
	///		<item>
	///			<term>System.Enum</term>
	///			<description><see cref="T:MatchKit.Patterns.EnumPattern"/></description>
	///		</item>
	///		<item>
	///			<term>System.Byte, System.SByte, System.Int16, System.UInt16, System.Int32, System.UInt32, 
	///				System.Int64, System.UInt64, System.Decimal, System.Single, System.Double</term>
	///			<description><see cref="T:MatchKit.Patterns.NumberPattern"/></description>
	///		</item>
	///		<item>
	///			<term>Any matchable class</term>
	///			<description><see cref="T:MatchKit.ObjectMapping.Patterns.ObjectPattern"/></description>
	///		</item>
	///		<item>
	///			<term>Any allocable (not abstract) IPattern inherited class</term>
	///			<description>The pattern itself is used; if member value is null, a new instance is created</description>
	///		</item>
	/// </list>
	/// <para>
	///	For a member of enumeration type, a mapping for the specific enumeration type is searched; if the specific registration 
	///	does not exists, a mapping for the generic <b>System.Enum</b> type is searched.
	/// </para>
	/// <para>
	/// To register a default mapping for any matchable class, use the special type <see cref="F:MatchKit.DefaultTypeMappings.MatchableClass"/>.
	/// <br />
	/// To register a default mapping for any IPattern inherited class, use the type <see cref="T:MatchKit.IPattern"/>. 
	/// It's not possible to register a default mapping for a specific IPattern inherited class.
	/// </para>
	/// <para>
	/// If the searched type has not a default mapping, a mapping for the generic <b>System.Object</b> type is searched.
	/// </para>
	/// </remarks>
	/// <conceptualLink target="7e345cd8-bbcc-4616-a3c9-abcc9943dabc" />
	/// <conceptualLink target="0a06b2a9-5f58-409a-ba62-639cfd77bca7" />
	public static class DefaultTypeMappings
	{
		/// <summary>
		/// Virtual class to manage the default mapping for a generic matchable class
		/// </summary>
		static class MatchableVirtualType { }

		/// <summary>
		/// Gets the special type to use for register a mapping for any matchable class.
		/// </summary>
		public static readonly Type MatchableClass = typeof(MatchableVirtualType);

		private static Dictionary<Type, TypeMappingHandler> _mappings = new Dictionary<Type, TypeMappingHandler>();

		static DefaultTypeMappings()
		{
			Register(typeof(string), StringMapping);
			Register(typeof(char), CharMapping);
			Register(typeof(Enum), EnumMapping);
			Register(typeof(byte), NumberMapping);
			Register(typeof(sbyte), NumberMapping);
			Register(typeof(short), NumberMapping);
			Register(typeof(ushort), NumberMapping);
			Register(typeof(int), NumberMapping);
			Register(typeof(uint), NumberMapping);
			Register(typeof(long), NumberMapping);
			Register(typeof(ulong), NumberMapping);
			Register(typeof(decimal), NumberMapping);
			Register(typeof(float), NumberMapping);
			Register(typeof(double), NumberMapping);
			Register(typeof(IPattern), PatternClassMapping);
			Register(MatchableClass, MatchableClassMapping);
		}

		/// <summary>
		/// Registers a default mapping for a type.
		/// </summary>
		/// <param name="key">Type to map.</param>
		/// <param name="mapping">Handler to manage the creation of the default pattern.</param>
		public static void Register(Type key, TypeMappingHandler mapping)
		{
			_mappings[key] = mapping;
		}

		/// <summary>
		/// Unregisters a default mapping for a type.
		/// </summary>
		/// <param name="key">Type to unmap.</param>
		public static bool Unregister(Type key)
		{
			return _mappings.Remove(key);
		}

		/// <summary>
		/// Checks if a default mapping for a type is registered.
		/// </summary>
		/// <param name="key">Type to check.</param>
		public static bool IsRegistered(Type key)
		{
			if (key == null)
				throw new ArgumentNullException("key", "key cannot be null");

			// se il mapping esiste, true
			if (_mappings.ContainsKey(key))
				return true;
			// altrimenti se non e' un Enum, e la key e' il base Enum, false
			if (key.IsEnum && key != typeof(Enum))
				return _mappings.ContainsKey(typeof(Enum));
			// verifco se c'e' un associazione generica
			return _mappings.ContainsKey(typeof(object));
		}

		/// <summary>
		/// Performs type mapping to create the related pattern.
		/// </summary>
		/// <param name="key">Type to search for.</param>
		/// <param name="type">Effective type to pass to creating method.</param>
		/// <param name="memberValue">Value of the member.</param>
		/// <param name="isArray">Value indicating whether the member is declared as array.</param>
		/// <returns>The created pattern.</returns>
		public static IPattern Map(Type key, Type type, object memberValue, bool isArray)
		{
			TypeMappingHandler mapping;
			// se il mapping non esiste
			if (!_mappings.TryGetValue(key, out mapping))
			{
				// altrimenti se e' un Enum, e la key non il base Enum, false
				if (key.IsEnum && key != typeof(Enum))
					_mappings.TryGetValue(typeof(Enum), out mapping);
				else
					_mappings.TryGetValue(typeof(object), out mapping);
			}
			if (mapping == null)
				throw new MatchKitMappingException(0, "Cannot create pattern for Type [" + key.FullName + "]; there is no mapping registered");
			return mapping(type, memberValue, isArray);
		}

		private static IPattern StringMapping(Type type, object memberValue, bool isArray)
		{
			if (isArray)
				throw new MatchKitMappingException(31, "Default mapping for type [{0}] does not support array declaration", type.FullName);
			if (memberValue == null)
				return new WordPattern();
			return new StringPattern((string)memberValue, true, false);
		}

		private static IPattern CharMapping(Type type, object memberValue, bool isArray)
		{
			if (isArray)
				throw new MatchKitMappingException(31, "Default mapping for type [{0}] does not support array declaration", type.FullName);
			if (memberValue == null)
				return new CharPattern();
			return new CharPattern((char)memberValue);
		}

		private static IPattern EnumMapping(Type type, object memberValue, bool isArray)
		{
			if (isArray)
				throw new MatchKitMappingException(31, "Default mapping for type [{0}] does not support array declaration", type.FullName);
			return new EnumPattern(type);
		}

		private static IPattern NumberMapping(Type type, object memberValue, bool isArray)
		{
			if (isArray)
				throw new MatchKitMappingException(31, "Default mapping for type [{0}] does not support array declaration", type.FullName);
			return new NumberPattern();
		}

		private static IPattern MatchableClassMapping(Type type, object memberValue, bool isArray)
		{
			if (memberValue == null)
			{
				return new ObjectPattern(type, isArray);
			}
			else
			{
				return new ObjectPattern(memberValue, isArray);
			}
		}

		private static IPattern PatternClassMapping(Type type, object memberValue, bool isArray)
		{
			if (isArray)
				throw new MatchKitMappingException(31, "Default mapping for type [{0}] does not support array declaration", type.FullName);

			if (!ReflectionUtils.IsAllocable(type, true))
				throw new MatchKitMappingException(1, "Cannot allocate the pattern [" + type.FullName + "]");

			if (memberValue != null)
				return (IPattern)memberValue;
			else
				return (IPattern)Utils.GetDefaultConstructor(type).Invoke(null);
		}
	}
}
